three.js 测距功能实现
2023/09/27 17:34:00
three.js 测距功能实现
three.js 测距功能实现
背景需求
- 对三维模型进行测距;
- 共两个按钮:测距、清空;
代码实现
html设置
<el-button type="primary" style="position: absolute; z-index: 999; right: 80px" @click="measureDis">测距</el-button> <el-button type="primary" style="position: absolute; z-index: 999; right: 5px" @click="clearDraw()">清除</el-button>data设置
data() { return { raycaster: new THREE.Raycaster(), mouse: new THREE.Vector2(), group: new THREE.Group(), labelRenderer: new CSS2DRenderer(), groups: [], points: [], line: null, measuring: false, }; }init初始化函数
init() { //普通2D标签 let contain = document.getElementById("container"); // 获取three.js的显示框id this.labelRenderer.setSize(contain.offsetWidth, contain.offsetHeight); // 设置three.js的显示框大小 this.labelRenderer.domElement.style.position = "absolute"; // 设置three.js的显示框位置 this.labelRenderer.domElement.style.top = "8%"; this.labelRenderer.domElement.style.right = "1%"; this.labelRenderer.domElement.style.pointerEvents = "none"; // 避免HTML标签遮挡三维场景的鼠标事件 document.body.appendChild(this.labelRenderer.domElement); // 将labelRenderer添加到文档主体 }render标签渲染
this.labelRenderer.render(this.scene, this.camera);mounted设置
mounted() { this.init(); this.animate(); window.addEventListener("resize", this.onWindowResize); // 监测click window.addEventListener("click", (event) => { if (this.measuring) { this.onclick(event); } }); this.scene.add(this.group); },methods设置
// 窗口大小改变时更新的渲染器和相机,保持渲染画面的正确 onWindowResize() { const newWidth = window.innerWidth; const newHeight = window.innerHeight; this.camera.aspect = newWidth / newHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(newWidth, newHeight); }, // click捕捉函数 onclick(event) { // 获取鼠标点击的坐标 const mouseX = event.clientX; const mouseY = event.clientY; // 获取three.js场景的容器 let container = document.getElementById("container"); // 将鼠标坐标转换为Three.js坐标系中的位置 this.mouse.x = ((mouseX - container.getBoundingClientRect().left) / container.offsetWidth) *2 - 1; this.mouse.y = -((mouseY - container.getBoundingClientRect().top) / container.offsetHeight) * 2 + 1; // 设置穿透的射线 this.raycaster.params.Points.threshold = 0.3; this.raycaster.setFromCamera(this.mouse, this.camera); // 获取射线和场景中对象的交点 const intersects = this.raycaster.intersectObjects( this.scene.children, false ); // 如果存在多个交点数则取第一个 if (intersects.length > 0) { const point = intersects[0].point; this.addPoint(point.x, point.y, point.z); } }, addPoint(x, y, z) { // 创建点对象 const point = this.createPoint(x, y, z); // 一个为场景对象 this.group.add(point); this.groups.push(point); console.log(this.group); // 将点的坐标保存到数组中 this.points.push(new THREE.Vector3(x, y, z)); // 更新测量线 this.updateMeasurementLine(); }, updateMeasurementLine() { if (this.points.length >= 2) { // 创建线的几何体和材质 const lineGeometry = new THREE.BufferGeometry().setFromPoints( this.points ); const lineMaterial = new THREE.LineBasicMaterial({ color: 0xf9f8ed, }); // 创建线对象 if (!this.line) { this.line = new THREE.Line(lineGeometry, lineMaterial); this.group.add(this.line); this.groups.push(this.line); } else { // this.line.geometry.dispose(); this.line.geometry = lineGeometry; } // 计算距离并显示在界面上 const distance = this.points[this.points.length - 2].distanceTo( this.points[this.points.length - 1] ); console.log(`距离: ${distance}`); // 添加CSS 2DObject标签 // 求中点坐标 let centerX = (this.points[this.points.length - 2].x + this.points[this.points.length - 1].x) / 2; let centerY = (this.points[this.points.length - 2].y + this.points[this.points.length - 1].y) / 2; let centerZ = (this.points[this.points.length - 2].z + this.points[this.points.length - 1].z) / 2; //创建标签 const container = document.getElementById("container"); const earthDiv = document.createElement("div"); earthDiv.className = "label_name"; // 定义的类名可以通过下面的style进行设置样式 earthDiv.textContent = distance.toFixed(5) + " m"; earthDiv.style.marginTop = "-1em"; const label2D = new CSS2DObject(earthDiv); label2D.name = distance.toFixed(0); label2D.position.set(centerX, centerY, centerZ); //标签标注在obj世界坐标 container.appendChild(label2D.element); this.group.add(label2D); //标签插入场景 this.groups.push(label2D); // this.labelRenderer.scene.add(label2D); this.labelRenderer.render(this.scene, this.camera); } }, measureDis() { this.measuring = !this.measuring; }, clearDraw() { console.log(this.scene.children); // 移除this.groups中保存的所有对象 // 注:不直接使用this.scene.children进行遍历,是因为遍历结果不全面无法得到页面所有Mesh有待商催 this.groups.forEach((object) => { console.log(object); if (object.type === "Mesh" || object.type === "Line") { console.log(object.type); // 释放几何体和样式资源 object.geometry.dispose(); object.material.dispose(); this.group.remove(object); if (object.type === "Line") { this.line = null; } } else { // let label = this.scene.getObjectByName(object.name); object.parent.remove(object); } }); this.points = []; this.measuring = !this.measuring; }, createPoint(x, y, z, config = { color: 0xf9f8ed, size: 0.8 }) { let mat = new THREE.MeshBasicMaterial({ color: config.color || 0xf9f8ed, }); let sphereGeometry = new THREE.SphereGeometry(config.size || 0.3, 32, 32); let sphere = new THREE.Mesh(sphereGeometry, mat); sphere.position.set(x, y, z); return sphere; },style设置
.label_name { color: #f9f8ed; font-size: 16px; z-index: 9999 !important; }